#include "find_entities.h"

#include "entity.h"
#include "player_spawn.h"

#include "tile.h"
#include "destructibletile.h"
#include "grass_tile.h"
#include "concrete_tile.h"
#include "concrete_2_tile.h"
#include "free_tile.h"
#include "sand_tile.h"
#include "sand_stone_tile.h"
#include "sand_stone_outside_tile.h"
#include "desert_box.h"
#include "snow_box.h"
#include "snow_inside_tile.h"
#include "snow_outside_tile.h"
#include "snow_tile.h"

#include "pickup.h"
#include "bomb_pickup.h"
#include "power_pickup.h"
#include "speed_pickup.h"
#include "bomb_down_pickup.h"
#include "power_down_pickup.h"
#include "speed_down_pickup.h"

namespace utils
{
    CEntity *findEntity(short int entityID,float fX, float fY, 
        float fZ, bool bVisible, vector<PARAM> params)
    {

        // A shortcut to check the tileID exists.
        #define CHECK_INSERT_ENTITY(entityID,entityType) \
        case entityID: \
            return dynamic_cast<CEntity*>(new entityType(fX, fY, fZ, bVisible));\
            break;    

        // Check that the tile id exists.
        switch (entityID)
        {
            case ENT_PLAYER_SPAWN:
            {
                ENTITY_ID nPlayerId = ENT_PLAYER_1;
                if (params.size() >= 1)
                {
                    nPlayerId = GET_ENTITY_ID_FROM_PARAM(params, 0);
                }
            
                return dynamic_cast<CEntity*>(new CPlayerSpawn(fX, fY, fZ, 
                                                               nPlayerId));
                break;
            }
            default:
            {
                stringstream sstream;
                sstream << "An entity by the id of "
                        << entityID
                        << " doesn't exist"
                        << ".\n\nPlease use a correct entity.\n\n";
                throw ErrMapLoader(sstream.str());
                break;
            }
        }

        // Undefine the shortcut.
        #undef CHECK_INSERT_ENTITY

        return nullptr;
    }

    CPickup *findPickup(short int pickupID, float fX, float fY, float fZ)
    {

        // A shortcut to check the tileID exists.
        #define CHECK_INSERT_PICKUP(pickupID, pickupType) \
        case pickupID: \
            return dynamic_cast<CPickup*>(new pickupType(fX, fY, fZ)); \
            break;
        // Check that the tile id exists.
        switch (pickupID)
            {
                CHECK_INSERT_PICKUP(ENT_PICKUP_BOMB, CBombPickup);
                CHECK_INSERT_PICKUP(ENT_PICKUP_POWER, CPowerPickup);
                CHECK_INSERT_PICKUP(ENT_PICKUP_SPEED, CSpeedPickup);
                CHECK_INSERT_PICKUP(ENT_PICKUP_BOMB_DOWN, CBombDownPickup);
                CHECK_INSERT_PICKUP(ENT_PICKUP_POWER_DOWN, CPowerDownPickup);
                CHECK_INSERT_PICKUP(ENT_PICKUP_SPEED_DOWN, CSpeedDownPickup);
                default:
                {
                    stringstream sstream;
                    sstream << "A pickup by the id of "
                            << pickupID
                            << " doesn't exist"
                            << ".\n\nPlease use a correct pickup.\n\n";
                    throw ErrMapLoader(sstream.str());
                    break;
                }
            }

        // Undefine the shortcut.
        #undef CHECK_INSERT_PICKUP

        return nullptr;
    }

    vector<PARAM> getEntityParams(CFileReader &file, int nAmount)
    {
        #define READ_TYPE(typedefinition, type, function) \
            case typedefinition:\
            {\
                type *value = new type; \
                *value = file.function(); \
                param.nType = typedefinition;\
                param.vpValue = reinterpret_cast<void*>(value);\
                break;\
            }
        vector<PARAM> params;
        for (int nParamIndex(0); nParamIndex < nAmount; ++nParamIndex)
        {
            short typeValue(file.readShort());
            PARAM param;
        
            switch (typeValue)
            {
                READ_TYPE(utils::TYPE_BOOL, bool, readBool);
                READ_TYPE(utils::TYPE_USHORT, unsigned short int, readShort);
                READ_TYPE(utils::TYPE_SSHORT, signed short int, readShort);
                READ_TYPE(utils::TYPE_ULONG, unsigned long int, readLong);
                READ_TYPE(utils::TYPE_SLONG, signed long int, readLong);
                READ_TYPE(utils::TYPE_FLOAT, float, readFloat);
                READ_TYPE(utils::TYPE_DOUBLE, double, readDouble);
                READ_TYPE(utils::TYPE_UCHAR, unsigned char, readChar);
                READ_TYPE(utils::TYPE_SCHAR, signed char, readChar);
                READ_TYPE(utils::TYPE_ENTITY_ID, ENTITY_ID, readEntityId);

                case utils::TYPE_STRING:
                {
                    short sizeOfString(file.readShort());
                    char *value = new char[sizeOfString];
                    strncpy(value, file.readString(sizeOfString).c_str(), 
                            sizeOfString);
                    param.nType = utils::TYPE_STRING;
                    param.vpValue = reinterpret_cast<void*>(value);
                    param.nDereferenceSize = sizeOfString;
                    break;
                }
            }
            params.push_back(param);
        }
        #undef READ_TYPE
        return params;
    }

    CTile *findTile(short int tileID, short int nHeight, 
                    short int nWidth, short int nDepth)
    {
        /**
         *
         * Returns a cube of tiles from the given point given. This is used to test
         * what tiles a bomb will effect.
         *
         * @param int height The height radius of the cube.
         * @param int width The width radius of the cube.
         * @param int depth The depth radius of the cube.
         * @param int tileID The id of the tile that is being created.
         *
         * @return CTile* Returns the tile that has beem created with the tile id.
         */ 

        // A shortcut to check the tileID exists.
        #define CHECK_INSERT_TILE(tileID, tileType) \
        case tileID: \
            return dynamic_cast<CTile*>(new tileType(nHeight, nWidth, nDepth)); \
            break;

        // Check that the tile id exists.
        switch (tileID)
        {
            CHECK_INSERT_TILE(ENT_TILE_GRASS, CGrassTile);
            CHECK_INSERT_TILE(ENT_TILE_CONCRETE, CConcreteTile);
            CHECK_INSERT_TILE(ENT_TILE_FREE, CFreeTile);
            CHECK_INSERT_TILE(ENT_TILE_DESTRUCTIBLE, CDestructibleTile);
            CHECK_INSERT_TILE(ENT_TILE_CONCRETE_2, CConcreteTile2);
            CHECK_INSERT_TILE(ENT_TILE_SAND, CSandTile);
            CHECK_INSERT_TILE(ENT_TILE_SANDSTONE_OUTSIDE, CSandstone2Tile);
            CHECK_INSERT_TILE(ENT_TILE_SANDSTONE, CSandstoneTile);
            CHECK_INSERT_TILE(ENT_TILE_DESERT_BOX, CDesertBoxTile);

            CHECK_INSERT_TILE(ENT_TILE_SNOW_BOX, CSnowBoxTile);
            CHECK_INSERT_TILE(ENT_TILE_SNOW_OUTSIDE, CSnowOutsideTile);
            CHECK_INSERT_TILE(ENT_TILE_SNOW_INSIDE, CSnowInsideTile);
            CHECK_INSERT_TILE(ENT_TILE_SNOW, CSnowTile);

            default:
            {
                stringstream sstream;
                sstream << "A tile by the id of "
                        << tileID
                        << " doesn't exist"
                        << ".\n\nPlease use a correct tile.\n\n";
                throw ErrMapLoader(sstream.str());
                break;
            }
        }

        // Undefine the shortcut.
        #undef CHECK_INSERT_TILE

        return nullptr;
    }

    ErrMapLoader::ErrMapLoader(const string &szError)
    : std::runtime_error(szError)
    {
        /**
         * Crashes the program if the error is not handled and outputs the error 
         * message passed in.
         *
         * @param string szError The error message to output
         */
    }
}